人工智慧實務工作坊-手把手教你深度學習實務

神經網路介紹

林嶔 (Lin, Chin)

第一節:神經網路介紹(1)

F2_1

F2_2

第一節:神經網路介紹(2)

F2_3

– 神經細胞的構造如下,不論是何種神經元皆可分成:接收區、觸發區、傳導區和輸出區。

F2_4

F2_5

第一節:神經網路介紹(3)

F2_6

\[ \begin{align} \mbox{weighted sum} & = w_{0} + w_{1}x_1 + w_{2}x_2 + \dots \\ \hat{y} & = step(\mbox{weighted sum}) \end{align} \]

第一節:神經網路介紹(4)

– 讓我們使用MxNet來實現吧!這次我們同樣使用剛剛的IRIS資料集,請到這裡下載這份資料集

– 這次預測的任務比較不一樣,我們使用一部分的資料作為Training set,一部分的資料作為Testing set,而使用花萼(sepal)和花瓣(petal)的長度和寬度來預測鳶尾屬下的三個亞屬:山鳶尾(setosa)、變色鳶尾(versicolor)和維吉尼亞鳶尾(virginica):

F2_7

iris = read.csv('data/iris.csv')

X.array = array(t(as.matrix(iris[,-5])), dim = c(4, 150))
Y.array = array(t(model.matrix(~ -1 + factor(iris[,5]))), dim = c(3, 150))

set.seed(0)
TRAIN.seq = sample(1:150, 100)

TRAIN.X.array = X.array[,TRAIN.seq]
TRAIN.Y.array = Y.array[,TRAIN.seq]
TEST.X.array = X.array[,-TRAIN.seq]
TEST.Y.array = Y.array[,-TRAIN.seq]

第一節:神經網路介紹(5)

library(mxnet)

data = mx.symbol.Variable(name = 'data')
fc1 = mx.symbol.FullyConnected(data = data, num.hidden = 5, name = 'fc1')
act1 = mx.symbol.Activation(data = fc1, act.type = 'relu', name = 'act1')
fc2 = mx.symbol.FullyConnected(data = act1, num.hidden = 3, name = 'fc2')
out_layer = mx.symbol.SoftmaxOutput(data = fc2, name = 'out_layer')
my.eval.metric.mlogloss <- mx.metric.custom(
  name = "m-logloss", 
  function(real, pred) {
    real1 = as.numeric(as.array(real))
    pred1 = as.numeric(as.array(pred))
    pred1[pred1 <= 1e-6] = 1e-6
    pred1[pred1 >= 1 - 1e-6] = 1 - 1e-6
    return(-mean(real1 * log(pred1), na.rm = TRUE))
  }
)

mx.set.seed(0)

iris_model = mx.model.FeedForward.create(symbol = out_layer,
                                         X = TRAIN.X.array, y = TRAIN.Y.array,
                                         optimizer = "sgd", learning.rate = 0.05, momentum = 0.9,
                                         array.batch.size = 20, num.round = 100,
                                         ctx = mx.cpu(), 
                                         eval.metric = my.eval.metric.mlogloss)

第一節:神經網路介紹(6)

predict_Y = predict(iris_model, TEST.X.array, array.layout = "colmajor")
predict_cat = max.col(t(predict_Y))
real_cat = max.col(t(TEST.Y.array))

confusion_table = table(predict_cat, real_cat)
print(confusion_table)
##            real_cat
## predict_cat  1  2  3
##           1 18  0  0
##           2  0 13  0
##           3  0  2 17

第二節:卷積神經網路介紹(1)

– 但回到我們的手寫數字分類問題,當我們看到這些手寫數字時,我們一眼就能認出他們了,但從「圖片」到「概念」的過程真的這麼簡單嗎?

– 現在我們面對的是視覺問題,看來除了模擬大腦思考運作的過程之外,我們還需要模擬眼睛的作用!

第二節:卷積神經網路介紹(2)

F6_2

– 他們的研究發現,貓咪在受到不同形狀的圖像刺激時,感受野的腦部細胞會產生不同反應

F6_3

第二節:卷積神經網路介紹(3)

– 卷積器模擬了感受野最初的細胞,他們負責用來辨認特定特徵,他們的數學模式如下:

F6_4

– 「特徵圖」的意義是什麼呢?卷積器就像是最初級的視覺細胞,他們專門辨認某一種簡單特徵,那這個「特徵圖」上面數字越大的,就代表那個地方越符合該細胞所負責的特徵。

F6_5

第二節:卷積神經網路介紹(4)

F6_6

F6_7

第二節:卷積神經網路介紹(5)

– 我們想像有一張人的圖片,假定第一個卷積器是辨認眼睛的特徵,第二個卷積器是在辨認鼻子的特徵,第三個卷積器是在辨認耳朵的特徵,第四個卷積器是在辨認手掌的特徵,第五個卷積器是在辨認手臂的特徵

– 第1.2.3張特徵圖中數值越高的地方,就分別代表眼睛、鼻子、耳朵最有可能在的位置,那將這3張特徵圖合在一起看再一次卷積,是否就能辨認出人臉的位置?

– 第4.5張特徵圖中數值越高的地方,就分別代表手掌、手臂最有可能在的位置,那將這2張特徵圖合在一起看再一次卷積,是否就能辨認出的位置?

– 第4.5張特徵圖對人臉辨識同樣能起到作用,因為人臉不包含手掌、手臂,因此如果有個卷積器想要辨認人臉,他必須對第1.2.3張特徵圖做正向加權,而對第4.5張特徵圖做負向加權

F6_8

第三節:利用卷積神經網路做手寫數字辨識(1)

F6_9

第三節:利用卷積神經網路做手寫數字辨識(2)

– 請在這裡下載MNIST的手寫數字資料,並讓我們了解一下這筆資料的結構

library(data.table)

mnist = fread("data/MNIST.csv", data.table = FALSE)
mnist = data.matrix(mnist)

n.sample = dim(mnist)[1]

X = mnist[,-1]
X = t(X)
dim(X) = c(28, 28, 1, dim(mnist)[1])
for (i in 1:dim(X)[4]) {
  X[,,,i] = t(X[,,,i] )
}

Y = array(t(model.matrix(~ -1 + factor(mnist[,1]))), dim = c(10, n.sample))
library(OpenImageR)

imageShow(X[,,,25])

Y[,25]
##  [1] 0 0 1 0 0 0 0 0 0 0

第三節:利用卷積神經網路做手寫數字辨識(3)

– 另外,我們需要對X進行標準化(平均值33.4,標準差78.7),有些人可以嘗試不先標準化的結果,你會發現根本無法訓練!

print(mean(X))
## [1] 33.40891
print(sd(X))
## [1] 78.67774
X = (X - mean(X))/sd(X)

set.seed(0)
Train.sample = sample(1:n.sample, n.sample*0.6, replace = FALSE)

Train.X = X[,,,Train.sample]
dim(Train.X) = c(28, 28, 1, n.sample * 0.6)
Train.Y = Y[,Train.sample]

Test.X = X[,,,-Train.sample]
dim(Test.X) = c(28, 28, 1, n.sample * 0.4)
Test.Y = Y[,-Train.sample]
fwrite(x = data.table(mnist[Train.sample,]),
       file = 'data/train_data.csv',
       col.names = FALSE, row.names = FALSE)

fwrite(x = data.table(mnist[-Train.sample,]),
       file = 'data/test_data.csv',
       col.names = FALSE, row.names = FALSE)

第三節:利用卷積神經網路做手寫數字辨識(4)

# input
data <- mx.symbol.Variable('data')

# first conv
conv1 <- mx.symbol.Convolution(data=data, kernel=c(5,5), num_filter=10, name = 'conv1')
relu1 <- mx.symbol.Activation(data=conv1, act_type="relu")
pool1 <- mx.symbol.Pooling(data=relu1, pool_type="max",
                          kernel=c(2,2), stride=c(2,2))

# second conv
conv2 <- mx.symbol.Convolution(data=pool1, kernel=c(5,5), num_filter=20, name = 'conv2')
relu2 <- mx.symbol.Activation(data=conv2, act_type="relu")
pool2 <- mx.symbol.Pooling(data=relu2, pool_type="max",
                          kernel=c(2,2), stride=c(2,2))

# first fullc
flatten <- mx.symbol.Flatten(data=pool2)
fc1 <- mx.symbol.FullyConnected(data=flatten, num_hidden=150, name = 'fc1')
relu3 <- mx.symbol.Activation(data=fc1, act_type="relu")

# second fullc
fc2 <- mx.symbol.FullyConnected(data=relu3, num_hidden=10, name = 'fc2')

# Softmax
lenet <- mx.symbol.SoftmaxOutput(data = fc2, name = 'lenet')
  1. 原始圖片(28x28x1)要先經過10個5x5的「卷積器」(5x5x1x10)處理,將使圖片變成10張「一階特徵圖」(24x24x10)

  2. 接著這10張「一階特徵圖」(24x24x10)會經過ReLU,產生10張「轉換後的一階特徵圖」(24x24x10)

  3. 接著這10張「轉換後的一階特徵圖」(24x24x10)再經過2x2「池化器」(2x2)處理,將使圖片變成10張「降維後的一階特徵圖」(12x12x10)

– 第二層卷積組合

  1. 再將10張「降維後的一階特徵圖」(12x12x10)經過20個5x5的「卷積器」(5x5x10x20)處理,將使圖片變成20張「二階特徵圖」(8x8x20)

  2. 接著這20張「二階特徵圖」(8x8x20)會經過ReLU,產生20張「轉換後的二階特徵圖」(8x8x20)

  3. 接著這20張「轉換後的二階特徵圖」(8x8x20)再經過2x2「池化器」(2x2)處理,將使圖片變成20張「降維後的二階特徵圖」(4x4x20)

– 全連接層

  1. 將「降維後的二階特徵圖」(4x4x20)重新排列,壓製成「一階高級特徵」(320)

  2. 讓「一階高級特徵」(320)進入「隱藏層」,輸出「二階高級特徵」(150)

  3. 「二階高級特徵」(150)經過ReLU,輸出「轉換後的二階高級特徵」(150)

  4. 「轉換後的二階高級特徵」(150)進入「輸出層」,產生「原始輸出」(10)

  5. 「原始輸出」(10)經過Softmax函數轉換,判斷圖片是哪個類別

第三節:利用卷積神經網路做手寫數字辨識(5)

my.eval.metric.mlogloss <- mx.metric.custom(
  name = "m-logloss", 
  function(real, pred) {
    real1 = as.numeric(as.array(real))
    pred1 = as.numeric(as.array(pred))
    pred1[pred1 <= 1e-6] = 1e-6
    pred1[pred1 >= 1 - 1e-6] = 1 - 1e-6
    return(-mean(real1 * log(pred1), na.rm = TRUE))
  }
)

mx.set.seed(0)

lenet_model = mx.model.FeedForward.create(symbol = lenet,
                                          X = Train.X, y = Train.Y,
                                          optimizer = "sgd", learning.rate = 0.05, momentum = 0.9,
                                          array.batch.size = 100, num.round = 20,
                                          ctx = mx.cpu(),
                                          eval.metric = my.eval.metric.mlogloss)
predict_Y = predict(lenet_model, Test.X)
predict_cat = max.col(t(predict_Y))
real_cat = max.col(t(Test.Y))

confusion_table = table(predict_cat, real_cat)
cat("Testing accuracy rate =", sum(diag(confusion_table))/sum(confusion_table))
## Testing accuracy rate = 0.9795833
print(confusion_table)
##            real_cat
## predict_cat    1    2    3    4    5    6    7    8    9   10
##          1  1656    1    4    2    3    4   11    0    8    7
##          2     0 1841    6    0    5    0    1    7    5    1
##          3     0    2 1625    4    1    0    1   15    6    0
##          4     0    0    3 1678    0    2    0    3    4    5
##          5     0    1    5    0 1568    0    7    1    3    7
##          6     1    0    2   42    0 1515    2    0    4   12
##          7     1    0    2    0    0   15 1638    0   18    0
##          8     1    3    5    5    5    8    0 1721    1   14
##          9     2    3    4    7    1    3    1    2 1622    3
##          10    2    0    0    4   23    4    0    4    4 1593

第四節:保存模型並讀取新的圖片進行預測(1)

#Save model
mx.model.save(lenet_model, "model/lenet", iteration = 0)

#Load model
lenet_model = mx.model.load("model/lenet", iteration = 0)

– 範例圖片可以從這裡下載

library(OpenImageR)

new_digit = readImage('digits/digit.1.jpeg')
imageShow(new_digit)

new_digit = rgb_2gray(new_digit)
dim(new_digit) = c(28, 28, 1, 1)

pred_y = predict(lenet_model, new_digit)
print(pred_y)
##              [,1]
##  [1,] 0.004551838
##  [2,] 0.009206435
##  [3,] 0.008242927
##  [4,] 0.014129665
##  [5,] 0.002852110
##  [6,] 0.010479977
##  [7,] 0.007251740
##  [8,] 0.004390656
##  [9,] 0.932146251
## [10,] 0.006748293

第四節:保存模型並讀取新的圖片進行預測(2)

new_digit = new_digit * 255
new_digit = (new_digit - 33.4) / 78.7

dim(new_digit) = c(28, 28, 1, 1)

pred_y = predict(lenet_model, new_digit)
print(round(pred_y, 3))
##       [,1]
##  [1,]    0
##  [2,]    0
##  [3,]    0
##  [4,]    0
##  [5,]    0
##  [6,]    0
##  [7,]    0
##  [8,]    0
##  [9,]    1
## [10,]    0
preprocessing = function (img) {
  
  img = rgb_2gray(img)
  img = img * 255
  img = (img - 33.4) / 78.7
  
  dim(img) = c(28, 28, 1, 1)
  
  img
  
}

new_digit = readImage('digits/digit.2.jpeg')
imageShow(new_digit)

new_digit = preprocessing(new_digit)
pred_y = predict(lenet_model, new_digit)
print(round(pred_y, 3))
##       [,1]
##  [1,]    0
##  [2,]    1
##  [3,]    0
##  [4,]    0
##  [5,]    0
##  [6,]    0
##  [7,]    0
##  [8,]    0
##  [9,]    0
## [10,]    0

小結

– 但你要注意你對圖像做過的所有前處理,在預測的時候全部都要重複一次!